package cloud.pangui.core.lock.resolver;

import cloud.pangui.core.lock.annotation.GlobalLock;
import cloud.pangui.core.lock.exception.GlobalLockException;
import javassist.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 解析注解上的表达式，预编译成LockResolver，
 * @Auther hepangui
 * @Date 2020/10/9 0009
 */
public class LockResolverFactory implements BeanFactoryAware {

	Logger logger = LoggerFactory.getLogger(LockResolver.class);

	BeanFactory beanFactory;

	/**
	 * map形式存放生成的Resolver
	 */
	private Map<Method, LockResolver> resolvers = new HashMap<>();

	/**
	 * 用于javassist 动态编译
	 */
	ClassPool classPool;

	{
		classPool = ClassPool.getDefault();
		classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
	}

	/**
	 * 根据方法获取对应的resolver
	 * @param method
	 * @return
	 */
	public LockResolver getResolver(Method method) {
		LockResolver lockResolver = this.resolvers.get(method);
		if (lockResolver == null) {
			throw new GlobalLockException("未能获取到LockResolver：" + method.toString());
		}
		return lockResolver;
	}


	/**
	 * 解析注解，预编译
	 * @param method
	 * @param annotation
	 */
	public void buildResolver(Method method, GlobalLock annotation) {
		if(annotation.expireTime() <annotation.timeout()){
			throw new GlobalLockException("[全局锁]注解配置有误：失效时间不能小于超时时间！"+method.toString());
		}

		String className = "LockResolver$" + method.getName()+ UUID.randomUUID().toString().replace("-","");
		try {
			CtClass ctClass = classPool.makeClass(className);
			ctClass.setSuperclass(classPool.get(LockResolver.class.getName()));
			//1.解析key
			if (!isEmpty(annotation.key())) {
				String strMethod = getKeyMethod(annotation.key(), method);
				if (logger.isTraceEnabled()) {
					logger.trace(className + "[key]");
					logger.trace("\n" + strMethod);
				}
				CtMethod ctm = CtMethod.make(strMethod, ctClass);
				ctClass.addMethod(ctm);
			}
			//2. 解析condtion
			if (!isEmpty(annotation.condition())) {
				String strMethod = getConditionMethod(annotation.condition(), method);
				if (logger.isTraceEnabled()) {
					logger.trace(className + "[condition]");
					logger.trace("\n" + strMethod);
				}
				CtMethod ctm = CtMethod.make(strMethod, ctClass);
				ctClass.addMethod(ctm);
			}
			//3. 解析callback
			if (!isEmpty(annotation.callback())) {
				String strMethod = getCallbackMethod(annotation.callback(), method);
				if (logger.isTraceEnabled()) {
					logger.trace(className + "[callback]");
					logger.trace("\n" + strMethod);
				}
				CtMethod ctm = CtMethod.make(strMethod, ctClass);
				ctClass.addMethod(ctm);
			}
			//创建代理后的实体
			Class<?> aClass = ctClass.toClass();
			LockResolver lockResolver = (LockResolver) aClass.newInstance();
			lockResolver.setMethod(method);
			this.resolvers.put(method, lockResolver);
		} catch (NotFoundException | CannotCompileException | IllegalAccessException | InstantiationException e) {
			throw new GlobalLockException("预编译失败：" + e.getMessage(), e);
		}

	}

	/**
	 * 生成回调方法  一种是有返回值的，一种是无返回值的，具体没有是跟加注解的方法是一致
	 *
	 * @param callback
	 * @param method
	 * @return
	 */
	private String getCallbackMethod(String callback, Method method) {
		StringBuilder sb = new StringBuilder("");
		Class<?> returnType = method.getReturnType();
		//无返回值
		if (returnType == Void.TYPE) {
			sb.append("public void callbackNoReturn(Object target,Object[] args){\n");
			sb.append(pattenToCode(callback, method, Void.TYPE));
		} else {
			//有返回值
			sb.append("public Object callback(Object target,Object[] args){\n");
			sb.append(pattenToCode(callback, method, returnType));
			sb.append("return retVal;\n");
			sb.append("}");
		}
		sb.append("}");
		return sb.toString();
	}

	/**
	 * 获取条件的方法
	 *
	 * @param condition
	 * @param method
	 * @return
	 */
	private String getConditionMethod(String condition, Method method) {
		condition = condition.replaceAll(" ", "");
		StringBuilder sb = new StringBuilder("");
		sb.append("public boolean checkConidtion(Object target,Object[] args){\n");
		if (condition.charAt(0) == '!') {
			condition = condition.substring(1);
			sb.append(pattenToCode(condition, method, boolean.class));
			sb.append("retVal = !retVal;\n");
		} else {
			sb.append(pattenToCode(condition, method, boolean.class));
		}
		sb.append("return retVal;\n").append("}");
		return sb.toString();
	}

	/**
	 * 获取key的方法
	 *
	 * @param keyPatten
	 * @param method
	 * @return
	 */
	private String getKeyMethod(String keyPatten, Method method) {
		StringBuilder sb = new StringBuilder("");
		sb.append("public String resolverKey(Object target , Object [] args){\n");
		String[] split = keyPatten.replaceAll(" ", "").split("\\+");
		sb.append("StringBuilder sb = new StringBuilder(\"\");\n");
		for (String s : split) {
			sb.append("{\n");
			sb.append(pattenToCode(s, method, String.class));
			sb.append("sb.append(retVal);\n").append("}\n");
		}
		sb.append("return sb.toString();\n")
				.append("}");
		return sb.toString();
	}

	/**
	 * 解析表达式转换为对应的代码
	 *
	 * @param s              需要解析的表达式
	 * @param annoMethod     方法
	 * @param needReturnType 需要的返回值 String，boolean, void
	 * @return
	 */
	private String pattenToCode(String s, Method annoMethod, Class needReturnType) {
		StringBuilder sb = new StringBuilder("");
		Class<?>[] parameterTypes = annoMethod.getParameterTypes();
		String clzName = annoMethod.getDeclaringClass().getName();
		if (String.class.equals(needReturnType)) {
			//解析key值是的特殊值处理
			if ("#target".equals(s)) {
				return "String retVal = target.getClass().getName();\n";
			} else if ("#method".equals(s)) {
				return "String retVal = method.getName();\n";
			} else if ("#args".equals(s)) {
				return "String retVal = java.util.Arrays.toString(args);\n";
			}
		}
		try {
			if (s.startsWith("#args")) {
				/*判断返回值与类型是否一致*/
				int index = Integer.parseInt(s.substring(s.indexOf("[") + 1, s.indexOf("]")));
				if (s.indexOf(".") > -1) {
					Class clz = parameterTypes[index];
					String methodName = s.substring(s.indexOf(".") + 1, s.indexOf("("));
					return getReturnStr(s, needReturnType, annoMethod, addParaTypeToArgs("args[" + index + "]", clzName, parameterTypes), clz, methodName);
				} else {
					if (boolean.class.equals(needReturnType) && parameterTypes[index].equals(boolean.class)) {
						sb.append("boolean retVal = ")
								.append(addParaTypeToArgs("args[" + index + "]", clzName, parameterTypes))
								.append(";\n");
					} else if (String.class.equals(needReturnType) && parameterTypes[index].equals(String.class)) {
						sb.append("String retVal = ")
								.append(addParaTypeToArgs("args[" + index + "]", clzName, parameterTypes))
								.append(";\n");
					} else if (String.class.equals(needReturnType) && !parameterTypes[index].equals(String.class)) {
						if (parameterTypes[index].isPrimitive()) {
							sb.append("String retVal = String.valueOf(args[").append(index).append("]);\n");
						} else {
							sb.append("String retVal = java.util.Arrays.toString(args[").append(index).append("]);\n");
						}
					} else {
						throw new GlobalLockException("表达式错误:" + annoMethod.getDeclaringClass().getName() + "#" + annoMethod.getName() + "->" + s);
					}
					return sb.toString();
				}
			} else //$开头为静态类
				if (s.startsWith("$")) {
					String packageName = getPackageName(s);
					String temp = s.replace(packageName + ".", "");
					String clazzName = temp.substring(1, temp.indexOf("."));
					Class clz = Class.forName(packageName + "." + clazzName);
					temp = s.replace(packageName + ".", "").replace(clazzName + ".", "");
					String methodName = temp.substring(1, temp.indexOf("("));
					String callObj = packageName + "." + clazzName;
					sb.append(getReturnStr(s, needReturnType, annoMethod, callObj, clz, methodName));
					return sb.toString();
				} else if (s.startsWith("#")) {
					Class<?> clz;
					//#target 获取当前serviceBean对象
					if (s.startsWith("#target")) {
						clz = annoMethod.getDeclaringClass();
						String className = clz.getName();
						sb.append(className).append(" serviceBean = ").append("(").append(className).append(")target;\n");
					} else {
						//#加service名称，获取某个service对象
						String beanName = s.substring(1, s.indexOf("."));
						Object bean = beanFactory.getBean(beanName);
						if (bean == null) {
							throw new GlobalLockException("bean不存在：" + annoMethod.getDeclaringClass().getName() + "#" + annoMethod.getName() + "->" + s);
						}
						clz = bean.getClass();
						String className = clz.getName();
						sb.append(className).append(" serviceBean = ").append("(").append(className)
								.append(")cloud.pangui.core.spring.SpringUtils.getBean(\"").append(beanName).append("\");\n");
					}
					//
					String methodName = s.substring(s.indexOf(".") + 1, s.indexOf("("));
					sb.append(getReturnStr(s, needReturnType, annoMethod, "serviceBean", clz, methodName));
					return sb.toString();
				} else {
					//返回值
					if (!String.class.equals(needReturnType)) {
						throw new GlobalLockException("表达式返回值错误" + annoMethod.getDeclaringClass().getName() + "#" + annoMethod.getName() + "->" + s);
					}
					sb.append("String retVal = ").append("\"" + s + "\";\n");
					return sb.toString();
				}
		} catch (ClassNotFoundException e) {
			throw new GlobalLockException("类名解析错误：" + annoMethod.getDeclaringClass().getName() + "#" + annoMethod.getName() + "->" + s);
		} catch (NoSuchMethodException e) {
			throw new GlobalLockException("方法名解析错误：" + annoMethod.getDeclaringClass().getName() + "#" + annoMethod.getName() + "->" + s);
		} catch (Exception e) {
//			e.printStackTrace();
			logger.error(e.getMessage(), e);
			throw new GlobalLockException("表达式解析错误：" + annoMethod.getDeclaringClass().getName() + "#" + annoMethod.getName() + "->" + s);
		}
	}

	/**
	 * 解析表达式并生成语句，这里是抽取的公共部分
	 * $isEmpty(str)    #target.xxx(args[0])
	 * 上述两个表达式其实就只是调用方不一样，所以在调用这个方法前
	 * 先获取的callObject的字符串，然后传进来
	 */
	private String getReturnStr(String s, Class needReturnType, Method annonMethod, String callObject, Class clz, String methodName) throws NoSuchMethodException {
		StringBuilder sb = new StringBuilder("");
		String clzName = clz.getName();
		String params = s.substring(s.indexOf("(") + 1, s.indexOf(")"));
		Class<?>[] parameterTypes = annonMethod.getParameterTypes();
		Class[] paraTypes = getParaTypes(params, annonMethod);
		Method method1 = getMethodByNameAndPara(clz, methodName, paraTypes);
		Class<?> returnType = method1.getReturnType();
		if (returnType.equals(Void.TYPE) && needReturnType.equals(Void.TYPE)) {
			sb.append(callObject).append(".").append(methodName)
					.append("(").append(addParaTypeToArgs(params, clzName, parameterTypes)).append(");\n");
		} else if (boolean.class.equals(needReturnType) && boolean.class.equals(returnType)) {
			sb.append("boolean retVal = ");
			sb.append(callObject).append(".").append(methodName)
					.append("(").append(addParaTypeToArgs(params, clzName, parameterTypes)).append(");\n");
		} else if (String.class.equals(needReturnType) && String.class.equals(returnType)) {
			sb.append("String retVal = ");
			sb.append(callObject).append(".").append(methodName)
					.append("(").append(addParaTypeToArgs(params, clzName, parameterTypes)).append(");\n");
		} else if (String.class.equals(needReturnType) && !String.class.equals(returnType)) {
//			if (returnType.isPrimitive()) {
				sb.append("String retVal = String.valueOf(");
//			} else {
//				sb.append("String retVal = JSONUtils.toString(");
//			}
			sb.append(callObject).append(".").append(methodName)
					.append("(").append(addParaTypeToArgs(params, clzName, parameterTypes)).append("));\n");
		} else {
			sb.append(needReturnType.getName()).append(" retVal = ");
			sb.append(callObject).append(".").append(methodName)
					.append("(").append(addParaTypeToArgs(params, clzName, parameterTypes)).append(");\n");
		}
		return sb.toString();
	}

	/**
	 * 根据方法名和参数获取方法，校验方法合理性
	 * 第一步先根据具体的方法名和参数类获取方法
	 * 如果获取不到，遍历所有的方法（public）,判断参数是否是父类
	 * 比如说JsonUtils.toString(Object o) 这是根据方法名和String.class是不能直接获取的
	 */
	private Method getMethodByNameAndPara(Class clz, String methodName, Class[] paraTypes) throws NoSuchMethodException {
		Method method1 = null;
		try {
			method1 = clz.getMethod(methodName, paraTypes);
		} catch (NoSuchMethodException e) {
			Method[] methods = clz.getMethods();
			for (Method method2 : methods) {
				if (method2.getName().equals(methodName)) {
					Class<?>[] parameterTypes1 = method2.getParameterTypes();
					if (paraTypes.length == parameterTypes1.length) {
						boolean match = true;
						for (int i = 0; i < parameterTypes1.length; i++) {
							if (!parameterTypes1[i].isAssignableFrom(paraTypes[i])) {
								match = false;
							}
						}
						if (match) {
							method1 = method2;
							break;
						}
					}
				}
			}
		}
		if (method1 == null) {
			throw new NoSuchMethodException(methodName);
		}
		return method1;
	}

	/**
	 * Object []args  里面的对象都是Object，当传入方法是，需要将其转换为原来的类
	 * 其中，如果是基本类型，不能直接强转，要转成包装类之后再拆箱
	 *
	 * @param s
	 * @param clzName
	 * @param parameterTypes
	 * @return
	 */
	private String addParaTypeToArgs(String s, String clzName, Class<?>[] parameterTypes) {
		if (isEmpty(s)) {
			return s;
		}
		s = s.replace("target", "((" + clzName + ")target)");
		for (int i = 0; i < parameterTypes.length; i++) {
			if (parameterTypes[i].isPrimitive()) {
				switch (parameterTypes[i].getName()) {
					case "boolean":
						s = s.replace("args[" + i + "]", "((" + "Boolean" + ")" + "(args[" + i + "])).booleanValue()");
						break;
					case "int":
						s = s.replace("args[" + i + "]", "((" + "Integer" + ")" + "(args[" + i + "])).intValue()");
						break;
					case "char":
						s = s.replace("args[" + i + "]", "((" + "Character" + ")" + "(args[" + i + "])).charValue()");
						break;
					case "byte":
						s.replace("args[" + i + "]", "((" + "Byte" + ")" + "(args[" + i + "])).byteValue()");
						break;
					case "long":
						s = s.replace("args[" + i + "]", "((" + "Long" + ")" + "(args[" + i + "])).longValue()");
						break;
					case "double":
						s = s.replace("args[" + i + "]", "((" + "Double" + ")" + "(args[" + i + "])).doubleValue()");
						break;
					case "float":
						s = s.replace("args[" + i + "]", "((" + "Float" + ")" + "(args[" + i + "])).floatValue()");
						break;
					case "short":
						s = s.replace("args[" + i + "]", "((" + "Short" + ")" + "(args[" + i + "])).shortValue()");
						break;
				}
			} else {
				s = s.replace("args[" + i + "]", "((" + parameterTypes[i].getName() + ")" + "(args[" + i + "]))");
			}
		}
		return s;
	}

	/**
	 * 解析类名，预留几个常用的Utils，其他为默认包或者写完整的包
	 *
	 * @param s
	 * @return
	 */
	private static String getPackageName(String s) {
		char c = s.charAt(1);
		if (c >= 'A' && c <= 'Z') {
			if (s.startsWith("$StringUtils") ||
					s.startsWith("$DateUtils") ||
					s.startsWith("$MapUtils")) {
				return "cloud.pangui.core.util";
			} else {
				return "java.lang";
			}
		} else {
			return s.substring(1, s.substring(0, s.lastIndexOf(".")).lastIndexOf("."));
		}
	}

	/**
	 * 获取括号内参数表达式的类型数据
	 * 比如说args[0],args[3]  ==>  int  String
	 *
	 * @param params
	 * @param method
	 * @return
	 */
	private Class[] getParaTypes(String params, Method method) {
		if (isEmpty(params)) {
			return new Class[0];
		}

		String[] split = params.split(",");
		Class[] pataType = new Class[split.length];
		for (int i = 0; i < split.length; i++) {
			String s = split[i];
			if ("target".equals(s)) {
				pataType[i] = method.getDeclaringClass();
			} else if ("method".equals(s)) {
				pataType[i] = Method.class;
			} else if ("args".equals(s)) {
				pataType[i] = Object[].class;
			} else if (s.startsWith("args")) {
				int index = Integer.parseInt(s.substring(s.indexOf("[") + 1, s.indexOf("]")));
				pataType[i] = method.getParameterTypes()[index];
			} else {
				throw new GlobalLockException("参数表达式解析错误：" + method.getDeclaringClass().getName() + "#" + method.getName() + "->" + s);

			}
		}
		return pataType;
	}


	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}

	private boolean isEmpty(String key) {
		return key == null || "".equals(key);
	}
}
